//
//  main.m
//  CameraRPi
//
//  Created by Agrond Pham on 8/29/13.
//  Copyright (c) 2013 Khanh Hoi. All rights reserved.
//

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <iostream>
#include <string>
#include <pthread.h>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include "extend/SimpleIni.h"
#include "extend/mongoose.h"

using namespace std;
using namespace cv;

#define WEB_PLUGIN 1
#define SAVEVIDEO 0
#define SHOWVIDEO 0


Mat img;
int     is_data_ready = 0,width,height,key=0;;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;


#if WEB_PLUGIN==1
Mat img_for_web;
int is_data_ready_for_web = 0;
pthread_mutex_t mutexWebInterface = PTHREAD_MUTEX_INITIALIZER;
std::vector<unsigned char> jbuf;
#endif



struct arg_struct {
    int arg1;
    int arg2;
};


// This function will be called by mongoose on every new request.
#if WEB_PLUGIN==1
bool EndsWith(const string& a,const string& b){
    if(b.size()>a.size())return false;
    return equal(a.begin()+a.size()-b.size(),a.end(),b.begin());
}
bool BeginWith(const string& a,const string& b){
    if(b.size()>a.size())return false;
    return equal(b.begin(),b.end(),a.begin());
}
string CharToStr(const char* inputChar){
    string output(inputChar);
    return output;
}
static void *callbacks(enum mg_event event,
                      struct mg_connection *conn) {
    const struct mg_request_info *request_info = mg_get_request_info(conn);  
    //check end of file
    if(EndsWith(request_info->uri, ".html")){
        char content[1024];
        int content_length = snprintf(content, sizeof(content),
                                      "<img src=/camera.mjpeg alt=Raspberry height=480 width=640> %d",
                                      request_info->remote_port);
        cout<< "uri:"<<request_info->uri<<endl;
        mg_printf(conn,
                  "HTTP/1.1 200 OK\r\n"
                  "Content-Type: text/html\r\n"
                  "Content-Length: %d\r\n"        // Always set Content-Length
                  "\r\n"
                  "%s",
                  content_length, content);
    }else if(EndsWith(request_info->uri, ".mjpeg")){
        if (event == MG_NEW_REQUEST) {
            mg_printf(conn,
                          "HTTP/1.1 200 OK\r\n"
                          "Content-Type: multipart/x-mixed-replace;boundary=b\r\n"
                          "Cache-Control: no-store\r\n"
                          "Pragma: no-cache\r\n"
                          "Connection: close\r\n"
                          "\r\n");
            while (true)
            {
                pthread_mutex_lock(&mutexWebInterface);
                if (is_data_ready_for_web) {
                    if( jbuf.size()<4 ||
                        (jbuf[0]!=0xff && jbuf[0]!=0xd8) ||
                        (jbuf[jbuf.size()-2]!=0xff && jbuf[jbuf.size()-1]!=0xd9))
                    {
                        usleep(10);
                        continue;
                    }
                    mg_printf(conn,
                              "--b\r\n"
                              "Content-Type: image/jpeg\r\n"
                              "Content-length: %d\r\n"
                              "\r\n",jbuf.size());
                    int ret=mg_write(conn,&jbuf[0], jbuf.size());
                    if(ret==0||ret==-1) return NULL;
                }
                is_data_ready_for_web = 0;
                pthread_mutex_unlock(&mutexWebInterface);
                //usleep(50);
            }
            return NULL;
        }
        return NULL;
    } else {
        return NULL;
    }
}
#endif
void* getData(void* arg){
    bool authenticated=false;
    int imgSize = (int)(img.total()*img.elemSize());
    struct arg_struct *agruments=(struct arg_struct *)arg;
    int connectSock=agruments->arg1;
    char sockData[imgSize];
    char sockAuthData[256];
    int  bytes=0;
    
    // make this thread cancellable using pthread_cancel() 
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    
    
    
    memset(sockData, 0x0, sizeof(sockData));
    cout<< "connectShock before loadData:"<< ntohs(connectSock) << endl;
    while (!authenticated) {
        cout << "-->Waiting receipt authentication " << endl;
        //for (int i = 0; i < 256; i += bytes) {
        if ((bytes = (int)recv(connectSock, sockAuthData, 2000, 0)) == 0) {
            
            //break;
        }
        //}
        printf("REV data %s /n",sockAuthData);
        if(BeginWith(CharToStr(sockAuthData),"KHNA")){
            cout << "-->Sending authentication " << endl;
            //check authentication
            //if connect is authenticated
            //send back accept
#ifdef __linux
            
            bytes = (int)send(connectSock, "ok", strlen("ok"), MSG_NOSIGNAL);
#else
            bytes = (int)send(connectSock, "ok", strlen("ok"), 0);
#endif
            authenticated=true;
            break;
        }

    }
    while(true){
        cout << "-->Waiting receipt image " << endl;
        for (int i = 0; i < imgSize; i += bytes) {
            if ((bytes = (int)recv(connectSock, sockData +i, imgSize  - i, 0)) == -1) {
                cout<<"recv failed"<<endl;
            }
        }
        // convert the received data to OpenCV's Mat format, thread safe
        pthread_mutex_lock(&mutex);
        for (int i = 0;  i < img.rows; i++) {
            for (int j = 0; j < img.cols; j++) {
                (img.row(i)).col(j) = (uchar)sockData[((img.cols)*i)+j];
            }
        }
        
        is_data_ready = 1;
        memset(sockData, 0x0, sizeof(sockData));
        pthread_mutex_unlock(&mutex);
        
        #if WEB_PLUGIN==1
        pthread_mutex_lock(&mutexWebInterface);
        //copy data to other frame
        img_for_web = (img.reshape(0,1));
        std::vector<unsigned char> buffer;
        std::vector<int> param(2);
        param[0]=CV_IMWRITE_JPEG_QUALITY;
        param[1]=95;
        imencode(".jpg",img,buffer,param);
        jbuf.swap(buffer);
        is_data_ready_for_web = 1;
        pthread_mutex_unlock(&mutexWebInterface);
        #endif
    }
    
    // have we terminated yet? 
    pthread_testcancel();
	
  	// no, take a rest for a while
    usleep(1000);
    
    
    return NULL;
    
}
void* videoThread(void* arg){
    CSimpleIni cSimpleIni;
    pthread_t thread_s1;
    //set Config file
    cSimpleIni.LoadFile("config.ini");
    
    
    struct arg_struct *agruments=(struct arg_struct *)arg;
    int connectSock=agruments->arg1;
    cout<<"check connectSock."<<ntohs(connectSock)<<endl;
    
    // make this thread cancellable using pthread_cancel() 
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    
    
    
    //set width and height for screen
	int width = atoi(cSimpleIni.GetValue("screen","width",""));
	int height = atoi(cSimpleIni.GetValue("screen","height",""));
 	
    
 	
    img = Mat::zeros( height,width, CV_8UC1);
    struct arg_struct args;
    args.arg1 = connectSock;    // run the streaming server as a separate thread 
    if (pthread_create(&thread_s1, NULL, getData, &args)) {
        cout<<"pthread_create failed."<<endl;
    }

    #if SAVEVIDEO==1
    VideoWriter writeVideo("Video.avi",CV_FOURCC('D','I','V','X'),5,cvSize(width,height),false);

    // Open input
    if (!writeVideo.isOpened())
    {
        cout  << "Could not open the input video: " << endl;
    }
    #endif
    while(key != 'q') {
        
        pthread_mutex_lock(&mutex);
        if (is_data_ready) {
            #if SHOWVIDEO==1
            imshow("stream_server", img);
            #endif
            #if SAVEVIDEO==1
            writeVideo <<img;
            #endif
            is_data_ready = 0;
        }
        pthread_mutex_unlock(&mutex);
        key = waitKey(10);
    }

    //cancel thread
    if (pthread_cancel(thread_s1)) {
        cout<<"pthread_cancel failed."<<endl;
    }
    
    destroyWindow("stream_server");
    
    
    
        
    // have we terminated yet? 
    pthread_testcancel();
	
  	// no, take a rest for a while
    usleep(1000);
    return NULL;
};
#if WEB_PLUGIN==1
void* mjpegThread(void* arg){
    
    //start server
    struct mg_context *ctx;
    // List of options. Last element must be NULL.
    const char *options[] = {"listening_ports", "8080",NULL};
    // Start the web server.
    ctx = mg_start(&callbacks, NULL, options);
    return NULL;
}
#endif
int main(int argc, char** argv)
{
    #if WEB_PLUGIN==1
    pthread_t thread_w;
    
    if (pthread_create(&thread_w, NULL, mjpegThread, NULL)) {
        cout << "pthread_create failed." << endl;
    }
    
    #endif
    
    CSimpleIni cSimpleIni;
    //set Config file
    cSimpleIni.LoadFile("config.ini");
    
    
    pthread_t thread_s;
    int     listenSock, connectSock;
    int 	listenPort;
    struct  sockaddr_in   serverAddr,  clientAddr;
    socklen_t clientAddrLen = sizeof(clientAddr);
    
    listenPort = atoi(cSimpleIni.GetValue("socket","port",""));
    
    if ((listenSock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        cout<<"socket() failed." << endl;
    }
    
    serverAddr.sin_family = PF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(listenPort);
    
    if (bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        cout<<"bind() failed" << endl;
    }
    
    if (listen(listenSock, 5) == -1) {
        cout<<"listen() failed." << endl;
    }
    
    #if SHOWVIDEO==1
    namedWindow("stream_server", CV_WINDOW_NORMAL|CV_GUI_NORMAL);
    #endif
    // start receiving images
    while(true)
    {
        cout << "-->Waiting for TCP connection on port " << listenPort << " ...\n\n";
	  	
		// accept a request from a client 
        if ((connectSock = accept(listenSock, (sockaddr*)&clientAddr, &clientAddrLen)) == -1) {
            cout << "accept() failed" << endl;
        }else{
            cout << "-->Receiving image from " << inet_ntoa(clientAddr.sin_addr) << ":" << ntohs(clientAddr.sin_port) << "...connectSock:"<< ntohs(connectSock) << endl;

            struct arg_struct args;
            args.arg1 = connectSock;
            // run the streaming server as a separate thread
            if (pthread_create(&thread_s, NULL, videoThread, &args)) {
                cout << "pthread_create failed." << endl;
            }
		}
        
    }
    return 0;
}















